/**
 * @file lv_span.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_span.h"

#if LV_USE_SPAN != 0

#include "../../../misc/lv_assert.h"

/*********************
 *      DEFINES
 *********************/
#define MY_CLASS &lv_spangroup_class

/**********************
 *      TYPEDEFS
 **********************/
typedef struct
{
    lv_span_t* span;
    const char* txt;
    const lv_font_t* font;
    uint16_t   bytes;
    lv_coord_t txt_w;
    lv_coord_t line_h;
    lv_coord_t letter_space;
} lv_snippet_t;

struct _snippet_stack
{
    lv_snippet_t    stack[LV_SPAN_SNIPPET_STACK_SIZE];
    uint16_t        index;
};

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lv_spangroup_destructor(const lv_obj_class_t* class_p,
                                    lv_obj_t* obj);
static void lv_spangroup_constructor(const lv_obj_class_t* class_p,
                                     lv_obj_t* obj);
static void lv_spangroup_event(const lv_obj_class_t* class_p, lv_event_t* e);
static void draw_main(lv_event_t* e);
static void refresh_self_size(lv_obj_t* obj);

static const lv_font_t* lv_span_get_style_text_font(lv_obj_t* par,
        lv_span_t* span);
static lv_coord_t lv_span_get_style_text_letter_space(lv_obj_t* par,
        lv_span_t* span);
static lv_color_t lv_span_get_style_text_color(lv_obj_t* par, lv_span_t* span);
static lv_opa_t lv_span_get_style_text_opa(lv_obj_t* par, lv_span_t* span);
static lv_opa_t lv_span_get_style_text_blend_mode(lv_obj_t* par,
        lv_span_t* span);
static int32_t lv_span_get_style_text_decor(lv_obj_t* par, lv_span_t* span);

static inline void span_text_check(const char** text);
static void lv_draw_span(lv_obj_t* obj, lv_draw_ctx_t* draw_ctx);
static bool lv_txt_get_snippet(const char* txt, const lv_font_t* font,
                               lv_coord_t letter_space,
                               lv_coord_t max_width, lv_text_flag_t flag, lv_coord_t* use_width,
                               uint32_t* end_ofs);

static void lv_snippet_clear(void);
static uint16_t lv_get_snippet_cnt(void);
static void lv_snippet_push(lv_snippet_t* item);
static lv_snippet_t* lv_get_snippet(uint16_t index);
static lv_coord_t convert_indent_pct(lv_obj_t* spans, lv_coord_t width);

/**********************
 *  STATIC VARIABLES
 **********************/
static struct _snippet_stack snippet_stack;

const lv_obj_class_t lv_spangroup_class  =
{
    .base_class = &lv_obj_class,
    .constructor_cb = lv_spangroup_constructor,
    .destructor_cb = lv_spangroup_destructor,
    .event_cb = lv_spangroup_event,
    .instance_size = sizeof(lv_spangroup_t),
    .width_def = LV_SIZE_CONTENT,
    .height_def = LV_SIZE_CONTENT,
};

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_obj_t* lv_spangroup_create(lv_obj_t* par)
{
    lv_obj_t* obj = lv_obj_class_create_obj(&lv_spangroup_class, par);
    lv_obj_class_init_obj(obj);
    return obj;
}

lv_span_t* lv_spangroup_new_span(lv_obj_t* obj)
{
    if (obj == NULL)
    {
        return NULL;
    }

    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    lv_span_t* span = _lv_ll_ins_tail(&spans->child_ll);
    LV_ASSERT_MALLOC(span);

    lv_style_init(&span->style);
    span->txt = (char*)"";
    span->static_flag = 1;
    span->spangroup = obj;

    refresh_self_size(obj);

    return span;
}

void lv_spangroup_del_span(lv_obj_t* obj, lv_span_t* span)
{
    if (obj == NULL || span == NULL)
    {
        return;
    }

    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    lv_span_t* cur_span;
    _LV_LL_READ(&spans->child_ll, cur_span)
    {
        if (cur_span == span)
        {
            _lv_ll_remove(&spans->child_ll, cur_span);

            if (cur_span->txt && cur_span->static_flag == 0)
            {
                lv_mem_free(cur_span->txt);
            }

            lv_style_reset(&cur_span->style);
            lv_mem_free(cur_span);
            break;
        }
    }

    refresh_self_size(obj);
}

/*=====================
 * Setter functions
 *====================*/

void lv_span_set_text(lv_span_t* span, const char* text)
{
    if (span == NULL || text == NULL)
    {
        return;
    }

    if (span->txt == NULL || span->static_flag == 1)
    {
        span->txt = lv_mem_alloc(strlen(text) + 1);
    }
    else
    {
        span->txt = lv_mem_realloc(span->txt, strlen(text) + 1);
    }

    span->static_flag = 0;
    strcpy(span->txt, text);

    refresh_self_size(span->spangroup);
}

void lv_span_set_text_static(lv_span_t* span, const char* text)
{
    if (span == NULL || text == NULL)
    {
        return;
    }

    if (span->txt && span->static_flag == 0)
    {
        lv_mem_free(span->txt);
    }

    span->static_flag = 1;
    span->txt = (char*)text;

    refresh_self_size(span->spangroup);
}

void lv_spangroup_set_align(lv_obj_t* obj, lv_text_align_t align)
{
    lv_obj_set_style_text_align(obj, align, LV_PART_MAIN);
}

void lv_spangroup_set_overflow(lv_obj_t* obj, lv_span_overflow_t overflow)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    if (spans->overflow == overflow)
    {
        return;
    }

    spans->overflow = overflow;
    lv_obj_invalidate(obj);
}

void lv_spangroup_set_indent(lv_obj_t* obj, lv_coord_t indent)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    if (spans->indent == indent)
    {
        return;
    }

    spans->indent = indent;

    refresh_self_size(obj);
}

void lv_spangroup_set_mode(lv_obj_t* obj, lv_span_mode_t mode)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    spans->mode = mode;
    lv_spangroup_refr_mode(obj);
}

void lv_spangroup_set_lines(lv_obj_t* obj, int32_t lines)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    spans->lines = lines;
    lv_spangroup_refr_mode(obj);
}

/*=====================
 * Getter functions
 *====================*/

lv_span_t* lv_spangroup_get_child(const lv_obj_t* obj, int32_t id)
{
    if (obj == NULL)
    {
        return NULL;
    }

    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    lv_ll_t* linked_list = &spans->child_ll;

    bool traverse_forwards = (id >= 0);
    int32_t cur_idx = 0;
    lv_ll_node_t* cur_node = linked_list->head;

    /*If using a negative index, start from the tail and use cur -1 to indicate the end*/
    if (!traverse_forwards)
    {
        cur_idx = -1;
        cur_node = linked_list->tail;
    }

    while (cur_node != NULL)
    {
        if (cur_idx == id)
        {
            return (lv_span_t*) cur_node;
        }

        if (traverse_forwards)
        {
            cur_node = (lv_ll_node_t*) _lv_ll_get_next(linked_list, cur_node);
            cur_idx++;
        }
        else
        {
            cur_node = (lv_ll_node_t*) _lv_ll_get_prev(linked_list, cur_node);
            cur_idx--;
        }
    }

    return NULL;
}

uint32_t lv_spangroup_get_child_cnt(const lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (obj == NULL)
    {
        return 0;
    }

    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    return _lv_ll_get_len(&(spans->child_ll));
}

lv_text_align_t lv_spangroup_get_align(lv_obj_t* obj)
{
    return lv_obj_get_style_text_align(obj, LV_PART_MAIN);
}

lv_span_overflow_t lv_spangroup_get_overflow(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    return spans->overflow;
}

lv_coord_t lv_spangroup_get_indent(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    return spans->indent;
}

lv_span_mode_t lv_spangroup_get_mode(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    return spans->mode;
}

int32_t lv_spangroup_get_lines(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    return spans->lines;
}

void lv_spangroup_refr_mode(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    if (spans->mode == LV_SPAN_MODE_EXPAND)
    {
        lv_obj_set_width(obj, LV_SIZE_CONTENT);
        lv_obj_set_height(obj, LV_SIZE_CONTENT);
    }
    else if (spans->mode == LV_SPAN_MODE_BREAK)
    {
        if (lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT)
        {
            lv_obj_set_width(obj, 100);
        }

        lv_obj_set_height(obj, LV_SIZE_CONTENT);
    }
    else if (spans->mode == LV_SPAN_MODE_FIXED)
    {
        /* use this mode, The user needs to set the size. */
        /* This is just to prevent an infinite loop. */
        if (lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT)
        {
            lv_obj_set_width(obj, 100);
        }

        if (lv_obj_get_style_height(obj, LV_PART_MAIN) == LV_SIZE_CONTENT)
        {
            lv_coord_t width = lv_obj_get_style_width(obj, LV_PART_MAIN);

            if (LV_COORD_IS_PCT(width))
            {
                width = 100;
            }

            lv_coord_t height = lv_spangroup_get_expand_height(obj, width);
            lv_obj_set_content_height(obj, height);
        }
    }

    refresh_self_size(obj);
}

lv_coord_t lv_spangroup_get_max_line_h(lv_obj_t* obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    lv_coord_t max_line_h = 0;
    lv_span_t* cur_span;
    _LV_LL_READ(&spans->child_ll, cur_span)
    {
        const lv_font_t* font = lv_span_get_style_text_font(obj, cur_span);
        lv_coord_t line_h = lv_font_get_line_height(font);

        if (line_h > max_line_h)
        {
            max_line_h = line_h;
        }
    }

    return max_line_h;
}

uint32_t lv_spangroup_get_expand_width(lv_obj_t* obj, uint32_t max_width)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    if (_lv_ll_get_head(&spans->child_ll) == NULL)
    {
        return 0;
    }

    uint32_t width = LV_COORD_IS_PCT(spans->indent) ? 0 : spans->indent;
    lv_span_t* cur_span;
    lv_coord_t letter_space = 0;
    _LV_LL_READ(&spans->child_ll, cur_span)
    {
        const lv_font_t* font = lv_span_get_style_text_font(obj, cur_span);
        letter_space = lv_span_get_style_text_letter_space(obj, cur_span);
        uint32_t j = 0;
        const char* cur_txt = cur_span->txt;
        span_text_check(&cur_txt);

        while (cur_txt[j] != '\0')
        {
            if (max_width > 0 && width >= max_width)
            {
                return max_width;
            }

            uint32_t letter      = _lv_txt_encoded_next(cur_txt, &j);
            uint32_t letter_next = _lv_txt_encoded_next(&cur_txt[j], NULL);
            uint16_t letter_w = lv_font_get_glyph_width(font, letter, letter_next);
            width = width + letter_w + letter_space;
        }
    }

    return width - letter_space;
}

lv_coord_t lv_spangroup_get_expand_height(lv_obj_t* obj, lv_coord_t width)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    if (_lv_ll_get_head(&spans->child_ll) == NULL || width <= 0)
    {
        return 0;
    }

    /* init draw variable */
    lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE;
    lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
    lv_coord_t max_width = width;
    lv_coord_t indent = convert_indent_pct(obj, max_width);
    lv_coord_t max_w  = max_width - indent; /* first line need minus indent */

    /* coords of draw span-txt */
    lv_point_t txt_pos;
    txt_pos.y = 0;
    txt_pos.x = 0 + indent; /* first line need add indent */

    lv_span_t* cur_span = _lv_ll_get_head(&spans->child_ll);
    const char* cur_txt = cur_span->txt;
    span_text_check(&cur_txt);
    uint32_t cur_txt_ofs = 0;
    lv_snippet_t snippet;   /* use to save cur_span info and push it to stack */
    memset(&snippet, 0, sizeof(snippet));

    int32_t line_cnt = 0;
    int32_t lines = spans->lines < 0 ? INT32_MAX : spans->lines;

    /* the loop control how many lines need to draw */
    while (cur_span)
    {
        int snippet_cnt = 0;
        lv_coord_t max_line_h =
            0;  /* the max height of span-font when a line have a lot of span */

        /* the loop control to find a line and push the relevant span info into stack  */
        while (1)
        {
            /* switch to the next span when current is end */
            if (cur_txt[cur_txt_ofs] == '\0')
            {
                cur_span = _lv_ll_get_next(&spans->child_ll, cur_span);

                if (cur_span == NULL)
                {
                    break;
                }

                cur_txt = cur_span->txt;
                span_text_check(&cur_txt);
                cur_txt_ofs = 0;
                /* maybe also cur_txt[cur_txt_ofs] == '\0' */
                continue;
            }

            /* init span info to snippet. */
            if (cur_txt_ofs == 0)
            {
                snippet.span = cur_span;
                snippet.font = lv_span_get_style_text_font(obj, cur_span);
                snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span);
                snippet.line_h = lv_font_get_line_height(snippet.font) + line_space;
            }

            /* get current span text line info */
            uint32_t next_ofs = 0;
            lv_coord_t use_width = 0;
            bool isfill = lv_txt_get_snippet(&cur_txt[cur_txt_ofs], snippet.font,
                                             snippet.letter_space,
                                             max_w, txt_flag, &use_width, &next_ofs);

            /* break word deal width */
            if (isfill && next_ofs > 0 && snippet_cnt > 0)
            {
                if (max_w < use_width)
                {
                    break;
                }

                uint32_t tmp_ofs = next_ofs;
                uint32_t letter = _lv_txt_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs);

                if (!(letter == '\0' || letter == '\n' || letter == '\r'
                        || _lv_txt_is_break_char(letter)))
                {
                    tmp_ofs = 0;
                    letter = _lv_txt_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], &tmp_ofs);

                    if (!(letter == '\0' || letter == '\n'  || letter == '\r'
                            || _lv_txt_is_break_char(letter)))
                    {
                        break;
                    }
                }
            }

            snippet.txt = &cur_txt[cur_txt_ofs];
            snippet.bytes = next_ofs;
            snippet.txt_w = use_width;
            cur_txt_ofs += next_ofs;

            if (max_line_h < snippet.line_h)
            {
                max_line_h = snippet.line_h;
            }

            snippet_cnt ++;
            max_w = max_w - use_width - snippet.letter_space;

            if (isfill  || max_w <= 0)
            {
                break;
            }
        }

        /* next line init */
        txt_pos.x = 0;
        txt_pos.y += max_line_h;
        max_w = max_width;
        line_cnt += 1;

        if (line_cnt >= lines)
        {
            break;
        }
    }

    txt_pos.y -= line_space;

    return txt_pos.y;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void lv_spangroup_constructor(const lv_obj_class_t* class_p,
                                     lv_obj_t* obj)
{
    LV_UNUSED(class_p);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    _lv_ll_init(&spans->child_ll, sizeof(lv_span_t));
    spans->indent = 0;
    spans->lines = -1;
    spans->mode = LV_SPAN_MODE_EXPAND;
    spans->overflow = LV_SPAN_OVERFLOW_CLIP;
    spans->cache_w = 0;
    spans->cache_h = 0;
    spans->refresh = 1;
}

static void lv_spangroup_destructor(const lv_obj_class_t* class_p,
                                    lv_obj_t* obj)
{
    LV_UNUSED(class_p);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    lv_span_t* cur_span = _lv_ll_get_head(&spans->child_ll);

    while (cur_span)
    {
        _lv_ll_remove(&spans->child_ll, cur_span);

        if (cur_span->txt && cur_span->static_flag == 0)
        {
            lv_mem_free(cur_span->txt);
        }

        lv_style_reset(&cur_span->style);
        lv_mem_free(cur_span);
        cur_span = _lv_ll_get_head(&spans->child_ll);
    }
}

static void lv_spangroup_event(const lv_obj_class_t* class_p, lv_event_t* e)
{
    LV_UNUSED(class_p);

    /* Call the ancestor's event handler */
    if (lv_obj_event_base(MY_CLASS, e) != LV_RES_OK)
    {
        return;
    }

    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t* obj = lv_event_get_target(e);
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    if (code == LV_EVENT_DRAW_MAIN)
    {
        draw_main(e);
    }
    else if (code == LV_EVENT_STYLE_CHANGED)
    {
        refresh_self_size(obj);
    }
    else if (code == LV_EVENT_SIZE_CHANGED)
    {
        refresh_self_size(obj);
    }
    else if (code == LV_EVENT_GET_SELF_SIZE)
    {
        lv_coord_t width = 0;
        lv_coord_t height = 0;
        lv_point_t* self_size = lv_event_get_param(e);

        if (spans->mode == LV_SPAN_MODE_EXPAND)
        {
            if (spans->refresh)
            {
                spans->cache_w = (lv_coord_t)lv_spangroup_get_expand_width(obj, 0);
                spans->cache_h = lv_spangroup_get_max_line_h(obj);
                spans->refresh = 0;
            }

            width = spans->cache_w;
            height = spans->cache_h;
        }
        else if (spans->mode == LV_SPAN_MODE_BREAK)
        {
            width = lv_obj_get_content_width(obj);

            if (self_size->y >= 0)
            {
                if (width != spans->cache_w || spans->refresh)
                {
                    height = lv_spangroup_get_expand_height(obj, width);
                    spans->cache_w = width;
                    spans->cache_h = height;
                    spans->refresh = 0;
                }
                else
                {
                    height = spans->cache_h;
                }
            }
        }
        else if (spans->mode == LV_SPAN_MODE_FIXED)
        {
            width =  self_size->x >= 0 ? lv_obj_get_content_width(obj) : 0;
            height = self_size->y >= 0 ? lv_obj_get_content_height(obj) : 0;
        }

        self_size->x = LV_MAX(self_size->x, width);
        self_size->y = LV_MAX(self_size->y, height);
    }
}

static void draw_main(lv_event_t* e)
{
    lv_obj_t* obj = lv_event_get_target(e);
    lv_draw_ctx_t* draw_ctx = lv_event_get_draw_ctx(e);

    lv_draw_span(obj, draw_ctx);
}

/**
 * @return true for txt fill the max_width.
 */
static bool lv_txt_get_snippet(const char* txt, const lv_font_t* font,
                               lv_coord_t letter_space, lv_coord_t max_width, lv_text_flag_t flag,
                               lv_coord_t* use_width, uint32_t* end_ofs)
{
    if (txt == NULL || txt[0] == '\0')
    {
        *end_ofs = 0;
        *use_width = 0;
        return false;
    }

    uint32_t ofs = _lv_txt_get_next_line(txt, font, letter_space, max_width,
                                         use_width, flag);
    *end_ofs = ofs;

    if (txt[ofs] == '\0' && *use_width < max_width)
    {
        return false;
    }
    else
    {
        return true;
    }
}

static void lv_snippet_push(lv_snippet_t* item)
{
    if (snippet_stack.index < LV_SPAN_SNIPPET_STACK_SIZE)
    {
        memcpy(&snippet_stack.stack[snippet_stack.index], item, sizeof(lv_snippet_t));
        snippet_stack.index++;
    }
    else
    {
        LV_LOG_ERROR("span draw stack overflow, please set LV_SPAN_SNIPPET_STACK_SIZE too larger");
    }
}

static uint16_t lv_get_snippet_cnt(void)
{
    return snippet_stack.index;
}

static lv_snippet_t* lv_get_snippet(uint16_t index)
{
    return &snippet_stack.stack[index];
}

static void lv_snippet_clear(void)
{
    snippet_stack.index = 0;
}

static const lv_font_t* lv_span_get_style_text_font(lv_obj_t* par,
        lv_span_t* span)
{
    const lv_font_t* font;
    lv_style_value_t value;
    lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_FONT, &value);

    if (res != LV_RES_OK)
    {
        font = lv_obj_get_style_text_font(par, LV_PART_MAIN);
    }
    else
    {
        font = (const lv_font_t*)value.ptr;
    }

    return font;
}

static lv_coord_t lv_span_get_style_text_letter_space(lv_obj_t* par,
        lv_span_t* span)
{
    lv_coord_t letter_space;
    lv_style_value_t value;
    lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_LETTER_SPACE,
                                     &value);

    if (res != LV_RES_OK)
    {
        letter_space = lv_obj_get_style_text_letter_space(par, LV_PART_MAIN);
    }
    else
    {
        letter_space = (lv_coord_t)value.num;
    }

    return letter_space;
}

static lv_color_t lv_span_get_style_text_color(lv_obj_t* par, lv_span_t* span)
{
    lv_style_value_t value;
    lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_COLOR, &value);

    if (res != LV_RES_OK)
    {
        value.color = lv_obj_get_style_text_color(par, LV_PART_MAIN);
    }

    return value.color;
}

static lv_opa_t lv_span_get_style_text_opa(lv_obj_t* par, lv_span_t* span)
{
    lv_opa_t opa;
    lv_style_value_t value;
    lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_OPA, &value);

    if (res != LV_RES_OK)
    {
        opa = (lv_opa_t)lv_obj_get_style_text_opa(par, LV_PART_MAIN);
    }
    else
    {
        opa = (lv_opa_t)value.num;
    }

    return opa;
}

static lv_blend_mode_t lv_span_get_style_text_blend_mode(lv_obj_t* par,
        lv_span_t* span)
{
    lv_blend_mode_t mode;
    lv_style_value_t value;
    lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_BLEND_MODE, &value);

    if (res != LV_RES_OK)
    {
        mode = (lv_blend_mode_t)lv_obj_get_style_blend_mode(par, LV_PART_MAIN);
    }
    else
    {
        mode = (lv_blend_mode_t)value.num;
    }

    return mode;
}

static int32_t lv_span_get_style_text_decor(lv_obj_t* par, lv_span_t* span)
{
    int32_t decor;
    lv_style_value_t value;
    lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_DECOR, &value);

    if (res != LV_RES_OK)
    {
        decor = (lv_text_decor_t)lv_obj_get_style_text_decor(par, LV_PART_MAIN);;
    }
    else
    {
        decor = (int32_t)value.num;
    }

    return decor;
}

static inline void span_text_check(const char** text)
{
    if (*text == NULL)
    {
        *text = "";
        LV_LOG_ERROR("occur an error that span text == NULL");
    }
}

static lv_coord_t convert_indent_pct(lv_obj_t* obj, lv_coord_t width)
{
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    lv_coord_t indent = spans->indent;

    if (LV_COORD_IS_PCT(spans->indent))
    {
        if (spans->mode == LV_SPAN_MODE_EXPAND)
        {
            indent = 0;
        }
        else
        {
            indent = (width * LV_COORD_GET_PCT(spans->indent)) / 100;
        }
    }

    return indent;
}

/**
 * draw span group
 * @param spans obj handle
 * @param coords coordinates of the label
 * @param mask the label will be drawn only in this area
 */
static void lv_draw_span(lv_obj_t* obj, lv_draw_ctx_t* draw_ctx)
{

    lv_area_t coords;
    lv_obj_get_content_coords(obj, &coords);

    lv_spangroup_t* spans = (lv_spangroup_t*)obj;

    /* return if not span */
    if (_lv_ll_get_head(&spans->child_ll) == NULL)
    {
        return;
    }

    /* return if no draw area */
    lv_area_t clip_area;

    if (!_lv_area_intersect(&clip_area, &coords, draw_ctx->clip_area))
    {
        return;
    }

    const lv_area_t* clip_area_ori = draw_ctx->clip_area;
    draw_ctx->clip_area = &clip_area;

    /* init draw variable */
    lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE;
    lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);;
    lv_coord_t max_width = lv_area_get_width(&coords);
    lv_coord_t indent = convert_indent_pct(obj, max_width);
    lv_coord_t max_w  = max_width - indent; /* first line need minus indent */
    lv_opa_t obj_opa = lv_obj_get_style_opa_recursive(obj, LV_PART_MAIN);

    /* coords of draw span-txt */
    lv_point_t txt_pos;
    txt_pos.y = coords.y1;
    txt_pos.x = coords.x1 + indent; /* first line need add indent */

    lv_span_t* cur_span = _lv_ll_get_head(&spans->child_ll);
    const char* cur_txt = cur_span->txt;
    span_text_check(&cur_txt);
    uint32_t cur_txt_ofs = 0;
    lv_snippet_t snippet;   /* use to save cur_span info and push it to stack */
    lv_memset_00(&snippet, sizeof(snippet));

    lv_draw_label_dsc_t label_draw_dsc;
    lv_draw_label_dsc_init(&label_draw_dsc);

    bool is_first_line = true;

    /* the loop control how many lines need to draw */
    while (cur_span)
    {
        bool is_end_line = false;
        bool ellipsis_valid = false;
        lv_coord_t max_line_h =
            0;  /* the max height of span-font when a line have a lot of span */
        lv_coord_t max_baseline = 0; /*baseline of the highest span*/
        lv_snippet_clear();

        /* the loop control to find a line and push the relevant span info into stack  */
        while (1)
        {
            /* switch to the next span when current is end */
            if (cur_txt[cur_txt_ofs] == '\0')
            {
                cur_span = _lv_ll_get_next(&spans->child_ll, cur_span);

                if (cur_span == NULL)
                {
                    break;
                }

                cur_txt = cur_span->txt;
                span_text_check(&cur_txt);
                cur_txt_ofs = 0;
                /* maybe also cur_txt[cur_txt_ofs] == '\0' */
                continue;
            }

            /* init span info to snippet. */
            if (cur_txt_ofs == 0)
            {
                snippet.span = cur_span;
                snippet.font = lv_span_get_style_text_font(obj, cur_span);
                snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span);
                snippet.line_h = lv_font_get_line_height(snippet.font) + line_space;
            }

            /* get current span text line info */
            uint32_t next_ofs = 0;
            lv_coord_t use_width = 0;
            bool isfill = lv_txt_get_snippet(&cur_txt[cur_txt_ofs], snippet.font,
                                             snippet.letter_space,
                                             max_w, txt_flag, &use_width, &next_ofs);

            if (isfill)
            {
                if (next_ofs > 0 && lv_get_snippet_cnt() > 0)
                {
                    /* To prevent infinite loops, the _lv_txt_get_next_line() may return incomplete words, */
                    /* This phenomenon should be avoided when lv_get_snippet_cnt() > 0 */
                    if (max_w < use_width)
                    {
                        break;
                    }

                    uint32_t tmp_ofs = next_ofs;
                    uint32_t letter = _lv_txt_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs);

                    if (!(letter == '\0' || letter == '\n' || letter == '\r'
                            || _lv_txt_is_break_char(letter)))
                    {
                        tmp_ofs = 0;
                        letter = _lv_txt_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], &tmp_ofs);

                        if (!(letter == '\0' || letter == '\n'  || letter == '\r'
                                || _lv_txt_is_break_char(letter)))
                        {
                            break;
                        }
                    }
                }
            }

            snippet.txt = &cur_txt[cur_txt_ofs];
            snippet.bytes = next_ofs;
            snippet.txt_w = use_width;
            cur_txt_ofs += next_ofs;

            if (max_line_h < snippet.line_h)
            {
                max_line_h = snippet.line_h;
                max_baseline = snippet.font->base_line;
            }

            lv_snippet_push(&snippet);
            max_w = max_w - use_width - snippet.letter_space;

            if (isfill || max_w <= 0)
            {
                break;
            }
        }

        /* start current line deal with */

        uint16_t item_cnt = lv_get_snippet_cnt();

        if (item_cnt == 0)      /* break if stack is empty */
        {
            break;
        }

        /* Whether the current line is the end line and does overflow processing */
        {
            lv_snippet_t* last_snippet = lv_get_snippet(item_cnt - 1);
            lv_coord_t next_line_h = last_snippet->line_h;

            if (last_snippet->txt[last_snippet->bytes] == '\0')
            {
                next_line_h = 0;
                lv_span_t* next_span = _lv_ll_get_next(&spans->child_ll, last_snippet->span);

                if (next_span)  /* have the next line */
                {
                    next_line_h = lv_font_get_line_height(lv_span_get_style_text_font(obj,
                                                          next_span)) + line_space;
                }
            }

            if (txt_pos.y + max_line_h + next_line_h - line_space > coords.y2 +
                    1)  /* for overflow if is end line. */
            {
                if (last_snippet->txt[last_snippet->bytes] != '\0')
                {
                    last_snippet->bytes = strlen(last_snippet->txt);
                    last_snippet->txt_w = lv_txt_get_width(last_snippet->txt, last_snippet->bytes,
                                                           last_snippet->font,
                                                           last_snippet->letter_space, txt_flag);
                }

                ellipsis_valid = spans->overflow == LV_SPAN_OVERFLOW_ELLIPSIS ? true : false;
                is_end_line = true;
            }
        }

        /*Go the first visible line*/
        if (txt_pos.y + max_line_h < clip_area.y1)
        {
            goto Next_line_init;
        }

        /* align deal with */
        lv_text_align_t align = lv_obj_get_style_text_align(obj, LV_PART_MAIN);

        if (align == LV_TEXT_ALIGN_CENTER || align == LV_TEXT_ALIGN_RIGHT)
        {
            lv_coord_t align_ofs = 0;
            lv_coord_t txts_w = is_first_line ? indent : 0;

            for (int i = 0; i < item_cnt; i++)
            {
                lv_snippet_t* pinfo = lv_get_snippet(i);
                txts_w = txts_w + pinfo->txt_w + pinfo->letter_space;
            }

            txts_w -= lv_get_snippet(item_cnt - 1)->letter_space;
            align_ofs = max_width > txts_w ? max_width - txts_w : 0;

            if (align == LV_TEXT_ALIGN_CENTER)
            {
                align_ofs = align_ofs >> 1;
            }

            txt_pos.x += align_ofs;
        }

        /* draw line letters */
        int i;

        for (i = 0; i < item_cnt; i++)
        {
            lv_snippet_t* pinfo = lv_get_snippet(i);

            /* bidi deal with:todo */
            const char* bidi_txt = pinfo->txt;

            lv_point_t pos;
            pos.x = txt_pos.x;
            pos.y = txt_pos.y + max_line_h - pinfo->line_h - (max_baseline -
                    pinfo->font->base_line);
            label_draw_dsc.color = lv_span_get_style_text_color(obj, pinfo->span);
            label_draw_dsc.opa = lv_span_get_style_text_opa(obj, pinfo->span);
            label_draw_dsc.font = lv_span_get_style_text_font(obj, pinfo->span);
            label_draw_dsc.blend_mode = lv_span_get_style_text_blend_mode(obj, pinfo->span);

            if (obj_opa < LV_OPA_MAX)
            {
                label_draw_dsc.opa = (uint16_t)((uint16_t)label_draw_dsc.opa * obj_opa) >> 8;
            }

            uint32_t txt_bytes = pinfo->bytes;

            /* overflow */
            uint16_t dot_letter_w = 0;
            uint16_t dot_width = 0;

            if (ellipsis_valid)
            {
                dot_letter_w = lv_font_get_glyph_width(pinfo->font, '.', '.');
                dot_width = dot_letter_w * 3;
            }

            lv_coord_t ellipsis_width = coords.x1 + max_width - dot_width;

            uint32_t j = 0;

            while (j < txt_bytes)
            {
                /* skip invalid fields */
                if (pos.x > clip_area.x2)
                {
                    break;
                }

                uint32_t letter      = _lv_txt_encoded_next(bidi_txt, &j);
                uint32_t letter_next = _lv_txt_encoded_next(&bidi_txt[j], NULL);
                int32_t letter_w = lv_font_get_glyph_width(pinfo->font, letter, letter_next);

                /* skip invalid fields */
                if (pos.x + letter_w + pinfo->letter_space < clip_area.x1)
                {
                    if (letter_w > 0)
                    {
                        pos.x = pos.x + letter_w + pinfo->letter_space;
                    }

                    continue;
                }

                if (ellipsis_valid && pos.x + letter_w + pinfo->letter_space > ellipsis_width)
                {
                    for (int ell = 0; ell < 3; ell++)
                    {
                        lv_draw_letter(draw_ctx, &label_draw_dsc, &pos, '.');
                        pos.x = pos.x + dot_letter_w + pinfo->letter_space;
                    }

                    if (pos.x <= ellipsis_width)
                    {
                        pos.x = ellipsis_width + 1;
                    }

                    break;
                }
                else
                {
                    lv_draw_letter(draw_ctx, &label_draw_dsc, &pos, letter);

                    if (letter_w > 0)
                    {
                        pos.x = pos.x + letter_w + pinfo->letter_space;
                    }
                }
            }

            /* draw decor */
            lv_text_decor_t decor = lv_span_get_style_text_decor(obj, pinfo->span);

            if (decor != LV_TEXT_DECOR_NONE)
            {
                lv_draw_line_dsc_t line_dsc;
                lv_draw_line_dsc_init(&line_dsc);
                line_dsc.color = label_draw_dsc.color;
                line_dsc.width = label_draw_dsc.font->underline_thickness ?
                                 pinfo->font->underline_thickness : 1;
                line_dsc.opa = label_draw_dsc.opa;
                line_dsc.blend_mode = label_draw_dsc.blend_mode;

                if (decor & LV_TEXT_DECOR_STRIKETHROUGH)
                {
                    lv_point_t p1;
                    lv_point_t p2;
                    p1.x = txt_pos.x;
                    p1.y = pos.y + ((pinfo->line_h - line_space) >> 1)  + (line_dsc.width >> 1);
                    p2.x = pos.x;
                    p2.y = p1.y;
                    lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
                }

                if (decor & LV_TEXT_DECOR_UNDERLINE)
                {
                    lv_point_t p1;
                    lv_point_t p2;
                    p1.x = txt_pos.x;
                    p1.y = pos.y + pinfo->line_h - line_space - pinfo->font->base_line -
                           pinfo->font->underline_position;
                    p2.x = pos.x;
                    p2.y = p1.y;
                    lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
                }
            }

            txt_pos.x = pos.x;
        }

Next_line_init:
        /* next line init */
        is_first_line = false;
        txt_pos.x = coords.x1;
        txt_pos.y += max_line_h;

        if (is_end_line || txt_pos.y > clip_area.y2 + 1)
        {
            draw_ctx->clip_area = clip_area_ori;
            return;
        }

        max_w = max_width;
    }

    draw_ctx->clip_area = clip_area_ori;
}

static void refresh_self_size(lv_obj_t* obj)
{
    lv_spangroup_t* spans = (lv_spangroup_t*)obj;
    spans->refresh = 1;
    lv_obj_invalidate(obj);
    lv_obj_refresh_self_size(obj);
}

#endif
